package com.service.socket.service;


import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.service.dao.enums.KlineTypeEnum;
import com.service.dao.model.CoinsPrice;
import com.service.dao.model.TradingSpotTrades;
import com.service.dao.po.KlineVo;
import com.service.socket.common.entity.KlineStatisticVo;
import com.service.socket.support.TradingSpotCache;
import com.service.socket.utils.DateUtil;
import com.service.utility.common.MathUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Slf4j
@Service
//@DS("slave_1")
public class TradingKlineService {
    @Autowired
    private TradingSpotCache spotCache;

    @Autowired
    private CoinsPriceService coinsPriceService;

    @Autowired
    private TradingSpotTradesService spotTradesService;

    public void handlerTrade(TradingSpotTrades tradeVo, KlineTypeEnum klineType) {
        KlineService klineService = KlineServiceFactory.getByType(klineType);

        long openTime = getOpenTime(tradeVo.getCreateTime(), klineType);

        KlineVo kline = klineService.getByOpenTimeAndSymbol(openTime, tradeVo.getSymbol());
        log.info("kline:type:{}->openTime:{}->kline{}", klineType, openTime, kline);
        if (ObjectUtils.isEmpty(kline)) {
            KlineVo lastKline = klineService.getLastOneBySymbol(tradeVo.getSymbol());

            kline = KlineVo.builder()
                    .symbol(tradeVo.getSymbol())
                    .timeOpen(openTime)
                    .timeClose(openTime + klineType.getValue() * 60)
                    .priceOpen(lastKline.getPriceClose())
                    .priceClose(tradeVo.getOrderPrice())
                    .priceHigh(lastKline.getPriceClose().max(tradeVo.getOrderPrice()))
                    .priceLow(lastKline.getPriceClose().min(tradeVo.getOrderPrice()))
                    .tradesNumber(tradeVo.getTradeNum())
                    .tradesTotal(tradeVo.getTotalMoney())
                    .createTime(new Date())
                    .tradesCount(1)
                    .build();
        } else {
            kline.setPriceClose(tradeVo.getOrderPrice());
            kline.setPriceHigh(kline.getPriceHigh().max(tradeVo.getOrderPrice()));
            kline.setPriceLow(kline.getPriceLow().min(tradeVo.getOrderPrice()));
            kline.setTradesNumber(kline.getTradesNumber().add(tradeVo.getTradeNum()));
            kline.setTradesTotal(kline.getTradesTotal().add(tradeVo.getTotalMoney()));
            kline.setTradesCount(kline.getTradesCount() + 1);
        }
        klineService.updateKline(kline);
    }

    private Long getOpenTime(Date tradesTime, KlineTypeEnum klineType) {
        switch (klineType) {
            case line_1:
            case line_15:
            case line_30:
            case line_60:
                return DateUtil.getOpenTime(tradesTime, klineType.getValue());
            case line_1440:
                return DateUtil.getDayOfStart2Sec(tradesTime);
            case line_10080:
                return DateUtil.getBeginDayOfWeek(tradesTime);
            default:
                return 0L;
        }
    }

    public List<Object[]> list4ws(String symbol, Date queryTime, KlineTypeEnum klineType, Integer limit) {
        KlineService klineService = KlineServiceFactory.getByType(klineType);
        Long openTime = getOpenTime(queryTime, klineType);
        List<KlineVo> resultList = klineService.listBySymbolAndLimit(symbol, openTime, limit);
        if (ObjectUtils.isEmpty(resultList)) {
            return new ArrayList<>();
        }

        resultList = polishingKline(openTime, resultList, klineType, limit);

        //数据意义：开盘(open)，收盘(close)，最低(lowest)，最高(highest)
        return resultList.stream().map(item -> new Object[]{
                this.formatDate(klineType, item.getTimeOpen()),
                item.getPriceOpen(), item.getPriceClose(),
                item.getPriceLow(), item.getPriceHigh()
        }).collect(Collectors.toList());
    }

    private String formatDate(KlineTypeEnum klineType, Long openLong) {
        openLong = openLong * 1000;
        String pattern = "";
        switch (klineType) {
            case line_1:
                pattern = "MM/dd HH:mm";
                break;
            case line_15:
            case line_30:
            case line_60:
                pattern = "MM/dd HH:mm";
                break;
            case line_1440:
            case line_10080:
                pattern = "MM/dd";
                break;
        }
        return DateFormatUtils.format(openLong, pattern);
    }

    private List<KlineVo> polishingKline(Long openTime, List<KlineVo> sourceList, KlineTypeEnum klineType, Integer limit) {
        KlineVo lastKline = sourceList.get(sourceList.size() - 1);

        Map<Long, KlineVo> sourceMap = sourceList.stream().collect(Collectors.toMap(KlineVo::getTimeOpen, x -> x));
        int step = 60 * klineType.getValue();

        //结果数据长度
        int len = (int) ((openTime - lastKline.getTimeOpen()) / (step)) + 1;
        List<KlineVo> returnList = new ArrayList<>(len + 1);
        Long nextTimeOpen = lastKline.getTimeOpen();
        for (int i = 0; i < len; i++) {
            if (sourceMap.containsKey(nextTimeOpen)) {
                lastKline = sourceMap.get(nextTimeOpen);
            } else {
                lastKline = KlineVo.builder()
                        .timeOpen(nextTimeOpen)
                        .timeClose(nextTimeOpen + step)
                        .priceOpen(lastKline.getPriceOpen())
                        .priceClose(lastKline.getPriceOpen())
                        .priceHigh(lastKline.getPriceOpen())
                        .priceLow(lastKline.getPriceOpen())
                        .tradesCount(0)
                        .tradesNumber(BigDecimal.ZERO)
                        .tradesTotal(BigDecimal.ZERO)
                        .build();
            }
            returnList.add(lastKline);
            nextTimeOpen += step;
        }
        return returnList.size() > limit ? returnList.subList(returnList.size() - limit - 1, returnList.size() - 1) : returnList;
    }

    public KlineStatisticVo getKlineStatistic(String symbol, Date queryTime) {

        Long openTime = getOpenTime(queryTime, KlineTypeEnum.line_1440);
        KlineService klineService = KlineServiceFactory.getByType(KlineTypeEnum.line_1440);
        KlineVo klineVo = klineService.getByOpenTimeAndSymbol(openTime, symbol);

        if (ObjectUtils.isEmpty(klineVo)) {
            klineVo = klineService.getLastOneBySymbol(symbol);
            if (ObjectUtils.isEmpty(klineVo)) {
                CoinsPrice price = coinsPriceService.getLastCoinPriceByCoinName(symbol.split("/")[0]);
                klineVo = KlineVo.builder().timeOpen(openTime)
                        .priceOpen(price.getStandardPrice())
                        .priceClose(price.getStandardPrice())
                        .priceHigh(price.getStandardPrice())
                        .priceLow(price.getStandardPrice())
                        .build();
            }
            klineVo.setTradesCount(0);
            klineVo.setTradesNumber(BigDecimal.ZERO);
            klineVo.setTradesTotal(BigDecimal.ZERO);
        }

        BigDecimal upPrice = klineVo.getPriceClose().subtract(klineVo.getPriceOpen());
        boolean isUp = upPrice.compareTo(BigDecimal.ZERO) >= 0;
        BigDecimal upRate = upPrice.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : MathUtil.div(upPrice, klineVo.getPriceOpen()).multiply(new BigDecimal(100));

        return KlineStatisticVo.builder()
                .uprate(upRate)
                .isUp(isUp)
                .symbol(symbol)
                .highPrice(klineVo.getPriceHigh())
                .lowPrice(klineVo.getPriceLow())
                .currentPrice(klineVo.getPriceClose())
                .totalTrade(klineVo.getTradesNumber())
                .build();
    }

}
